/*
 * Galaxium Messenger
 * Copyright (C) 2007 Ben Motmans <ben.motmans@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.Text;
using System.Collections;
using System.Collections.Generic;

using Anculus.Core;
using Galaxium.Core;

namespace Galaxium.Protocol
{
	public class Message : MessageChunk, IMessage
	{
		// FIXME: already in MessageUtility
		public static string[] UriStarts = { "http://", "https://", "file://", "ftp://", "mms://", "www.", "ftp." };
		static string _uriChars = ":;/.@&?=-_%#<>+~";
		
		MessageFlag _flags;
		IEntity _source, _dest;
		DateTime _timeStamp;
		
		static Dictionary<ISession, IMessageSplitter> _splitters = new Dictionary<ISession, IMessageSplitter> ();
		static Dictionary<string, IEmoticon> _emoticons = new Dictionary<string, IEmoticon> ();
		static List<ICustomEmoticon> _customEmoticons = new List<ICustomEmoticon> ();
		static string[] _splitSearch;
		
		public MessageFlag Flags
		{
			get { return _flags; }
			set { _flags = value; }
		}
		
		public DateTime TimeStamp
		{
			get { return _timeStamp; }
		}
		
		public IEntity Source
		{
			get { return _source; }
		}
		
		public IEntity Destination
		{
			get { return _dest; }
			set { _dest = value; }
		}
		
		public string Markup
		{
			get { return Join (Chunks, _source, _dest); }
		}
		
		public string Text
		{
			get
			{
				string txt = string.Empty;
				
				foreach (IMessageChunk chunk in Chunks)
				{
					if (chunk is ITextMessageChunk)
						txt += (chunk as ITextMessageChunk).Text;
				}
				
				return txt;
			}
			set
			{
				Chunks.Clear ();
				Chunks.Add (new TextMessageChunk (this, null, value));
			}
		}
		
		public Message (MessageFlag flags, IEntity source, IEntity dest, DateTime timeStamp)
			: base (null)
		{
			ThrowUtility.ThrowIfNull ("source", source);
			
			_flags = flags;
			_source = source;
			_dest = dest;
			_timeStamp = timeStamp;
		}
		
		public Message (MessageFlag flags, IEntity source, IEntity dest)
			: this (flags, source, dest, DateTime.Now)
		{
		}
		
		public void SetMarkup (string markup, IEnumerable<IEmoticon> emoticons)
		{
			Chunks.Clear ();
			Chunks.AddRange (Split (markup, _source, _dest, emoticons));
		}
		
		public static IMessageSplitter GetSplitter (ISession session)
		{
			lock (_splitters)  // NEEDINFO: isn't this always called from the GUI thread?
			{
				if (_splitters.ContainsKey (session))
					return _splitters[session];
				
				IProtocolFactory factory = ProtocolUtility.GetProtocolFactory (session.Account.Protocol);
				
				if (factory == null)
					return null;
				
				IMessageSplitter split = factory.CreateSplitter ();
				_splitters.Add (session, split);
				return split;
			}
		}
		
		public static List<IMessageChunk> Split (string text, IEntity source, IEntity dest, IEnumerable<IEmoticon> emoticons)
		{
			IMessageSplitter splitter = (source != null) ? GetSplitter (source.Session) : null;
			List<IMessageChunk> chunks;
			
			if (splitter != null)
				chunks = splitter.Split (text, source, dest);
			else
			{
				chunks = new List<IMessageChunk> ();
				chunks.Add (new TextMessageChunk (null, null, text));
			}
			
			foreach (IMessageChunk chunk in chunks)
				Split (chunk, source, dest, emoticons);
			
			// Remove any chunks which simply hold other chunks, serving no other purpose
			while ((chunks.Count == 1) &&
			       (chunks[0].Style == null) &&
			       (chunks[0] is ITextMessageChunk) &&
			       string.IsNullOrEmpty ((chunks[0] as ITextMessageChunk).Text))
			{
				chunks = chunks[0].Chunks;
			}
			
			return chunks;
		}
		
		static void Split (IMessageChunk chunk, IEntity source, IEntity dest, IEnumerable<IEmoticon> emoticons)
		{
			foreach (IMessageChunk child in chunk.Chunks)
				Split (child, source, dest, emoticons);
			
			if ((chunk is ITextMessageChunk) && !(chunk is IEmoticonMessageChunk) && !(chunk is IURIMessageChunk))
			{
				string text = (chunk as ITextMessageChunk).Text;
				Dictionary<string, IEmoticon> custEmoticons = new Dictionary<string, IEmoticon> ();
				SearchResult[] results = null;
				
				if (_splitSearch == null)
					BuildSplitSearch ();
				
				List<string> splitSearch = new List<string> (_splitSearch);
				
				if (emoticons != null)
				{
					foreach (IEmoticon emot in emoticons)
					{
						foreach (string equiv in emot.Equivalents)
							custEmoticons[equiv] = emot;
					}
				}
				
				foreach (ICustomEmoticon emot in _customEmoticons)
				{
					if (emot.Allow (source, dest))
					{
						foreach (string equiv in emot.Equivalents)
							custEmoticons[equiv] = emot;
					}
				}
				
				if (custEmoticons.Count > 0)
				{
					string[] keys = new string [custEmoticons.Count];
					custEmoticons.Keys.CopyTo (keys, 0);
					splitSearch.AddRange (keys);
				}
				
				results = FilterSplitSearch (SetSearch.SearchAll (text, splitSearch.ToArray ()));
				
				if (results.Length > 0)
				{
					(chunk as ITextMessageChunk).Text = string.Empty;
					int pos = 0;
					
					foreach (SearchResult result in results)
					{
						if (result.Index > pos)
						{
							// Add any text before the result
							IMessageChunk ch = new TextMessageChunk (chunk, null, text.Substring (pos, result.Index - pos));
							chunk.Chunks.Add (ch);
						}
						else if (result.Index < pos)
						{
							// Can happen if we match something within a URI
							continue;
						}
						
						if (custEmoticons.ContainsKey (result.Match))
						{
							IEmoticon emot = custEmoticons[result.Match];
							IMessageChunk ch = new EmoticonMessageChunk (chunk, null, emot, result.Match);
							chunk.Chunks.Add (ch);
							pos = result.Index + result.Length;
						}
						else if (_emoticons.ContainsKey (result.Match))
						{
							IEmoticon emot =  _emoticons[result.Match];
							IMessageChunk ch = new EmoticonMessageChunk (chunk, null, emot, result.Match);
							chunk.Chunks.Add (ch);
							pos = result.Index + result.Length;
						}
						else if ((result.Match == "\n") || (result.Match == "\r\n") || (result.Match == "\r"))
						{
							IMessageChunk ch = new TextMessageChunk (chunk, null, result.Match);
							chunk.Chunks.Add (ch);
							pos = result.Index + result.Length;
						}
						else
						{
							string uri = result.Match;
							pos = result.Index + result.Length;
							
							while ((pos < text.Length) && IsValidUriChar (text[pos]))
							{
								uri += text[pos];
								pos++;
							}
							
							IMessageChunk ch = new URIMessageChunk (chunk, null, uri);
							chunk.Chunks.Add (ch);
						}
					}
					
					if (pos < text.Length)
					{
						// Add any text remaining after all results
						
						IMessageChunk ch = new TextMessageChunk (chunk, null, text.Substring (pos));
						chunk.Chunks.Add (ch);
					}
				}
			}
		}
		
		public static string Join (IEnumerable<IMessageChunk> chunks, IEntity source, IEntity dest)
		{
			IMessageSplitter splitter = GetSplitter (source.Session);
			string markup = string.Empty;
			
			foreach (IMessageChunk chunk in chunks)
			{
				if (chunk is ITextMessageChunk)
				{
					if (!string.IsNullOrEmpty ((chunk as ITextMessageChunk).Text))
					{
						if (splitter != null)
							markup += splitter.ChunkMarkup (chunk);
						else
							markup += (chunk as ITextMessageChunk).Text;
					}
				}
				else if (splitter != null)
					markup += splitter.ChunkMarkup (chunk);
			}
			
			return markup;
		}
		
		public static string Strip (string text, IEntity source, IEntity dest)
		{
			// NEEDINFO: what is this for?
			return text;
		}
		
		public static void SetEmoticons (IEnumerable<IEmoticon> emoticons)
		{
			_emoticons.Clear ();
			
			if (emoticons != null)
			{
				foreach (IEmoticon emot in emoticons)
				{
					foreach (string equiv in emot.Equivalents)
						_emoticons[equiv] = emot;
				}
			}
			
			BuildSplitSearch ();
		}
		
		public static void SetCustomEmoticons (IEnumerable<ICustomEmoticon> emoticons)
		{
			_customEmoticons.Clear ();
			
			if (emoticons != null)
				_customEmoticons.AddRange (emoticons);
		}
		
		static void BuildSplitSearch ()
		{
			_splitSearch = new string [UriStarts.Length + _emoticons.Count + 3];
			Array.Copy (UriStarts, _splitSearch, UriStarts.Length);
			_emoticons.Keys.CopyTo (_splitSearch, UriStarts.Length);
			_splitSearch[_splitSearch.Length - 1] = "\r\n";
			_splitSearch[_splitSearch.Length - 2] = "\r";
			_splitSearch[_splitSearch.Length - 3] = "\n";
		}
		
		static SearchResult[] FilterSplitSearch (SearchResult[] results)
		{
			List<SearchResult> list = new List<SearchResult> ();
			list.AddRange (results);
			list.Sort (new SearchResultComparer ());
			
			int next = -1;
			for (int i = list.Count - 1; i >= 1; i--)
			{
				next = i - 1;
				
				if (list[next].Index == list[i].Index)
					list.RemoveAt (i); //keep only the longest match on a given start pos
				else if ((list[next].Index + list[next].Length) >= (list[i].Index + list[i].Length))
					list.RemoveAt (i); //make sure short matches don't appear anywhere inside longer matches
			}
			
			return list.ToArray ();
		}
		
		static bool IsValidUriChar (char ch)
		{
			return char.IsLetterOrDigit (ch) || (_uriChars.IndexOf (ch) >= 0);
		}
		
		class SearchResultComparer : IComparer<SearchResult>
		{
			public int Compare (SearchResult sr1, SearchResult sr2)
			{
				int comp = sr1.Index.CompareTo (sr2.Index); //Index ASC
				
				if (comp == 0)
					comp = sr2.Length.CompareTo (sr1.Length); //Length DESC
				
				return comp;
			}
		}
	}
}
